﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Net;
using RaptorEngine.Network;

namespace RaptorEngine.Managers
{
	public class NetworkManager
	{
        // Holds the networktool used for the manager.
	    public NetworkTools GameNetwork;

        //Holds the packetwriter.
	    private PacketWriter _packetWriter;
        // Holds the packetreader.
	    private PacketReader _packetReader;

        // Holds the networksession.
	    private NetworkSession _networkSession;
        // Collection that holds the availble sessions that can be joined.
	    private AvailableNetworkSessionCollection _availableNetworkSessions;

        // Maximum number of gamers on the local machine.
	    private int _maxLocalGamers;
        // Maximum number of network gamers that can connect.
	    private int _maxNetworkGamers;

        // Holds a number for simulating ms delay.
	    private int _ms;
        // Holds a number for simulating packetloss.
	    private float _packetLoss;

        // A list of the connected gamers.
	    public List<Gamer> Gamers;

        /// <summary>
        /// All the different states of the network.
        /// </summary>
        public enum NetworkState
        {
            /// <summary>
            /// Network offline
            /// </summary>
            Offline = 0,
            /// <summary>
            /// Login state
            /// </summary>
            Login,
            /// <summary>
            /// Hosting state
            /// </summary>
            Hosting,
            /// <summary>
            /// Joining state
            /// </summary>
            Joining,
            /// <summary>
            /// Waiting / Idle state
            /// </summary>
            Waiting,
            /// <summary>
            /// Playing state
            /// </summary>
            Playing,
            /// <summary>
            /// Ending the network session
            /// </summary>
            Ending
        }

        // Holds the current state.
        public NetworkState State { get; set; }
        // For setting status that can be used for debug.
	    public string Status;

        /// <summary>
        /// Initializes a new instance of the <see cref="NetworkManager" /> class.
        /// </summary>
        public NetworkManager()
        {
            _packetWriter = new PacketWriter();
            _packetReader = new PacketReader();

            Gamers = new List<Gamer>();
            State = NetworkState.Offline;

            _ms = 0;
            _packetLoss = 0;
        }

        /// <summary>
        /// Sets the network tool.
        /// </summary>
        /// <param name="tool">The tool.</param>
        public void SetNetworkTool(NetworkTools tool)
        {
            GameNetwork = tool;
            _maxLocalGamers = tool.MAX_LOCAL_GAMERS;
            _maxNetworkGamers = tool.MAX_NETWORK_GAMERS;
        }

        /// <summary>
        /// Updates the network, depending on current state. 
        /// </summary>
        /// <param name="gt">The gameTime.</param>
        public void Update(GameTime gt)
        {
            // If not logged in to Live, show the Live login.
            if (State == NetworkState.Login)
            {
                if (SignedInGamer.SignedInGamers.Count == 0 && !Guide.IsVisible)
                {
                    Guide.ShowSignIn(_maxLocalGamers, false);
                }
            }

            // Host a new game by creating a new session.
            if (State == NetworkState.Hosting)
            {
                if (_networkSession == null && SignedInGamer.SignedInGamers.Count == 1)
                {
                    CreateSession();
                }
            }

            // Join a session.
            if (State == NetworkState.Joining)
            {
                if (SignedInGamer.SignedInGamers.Count == 0 && !Guide.IsVisible)
                {
                    Guide.ShowSignIn(_maxLocalGamers, false);
                }
                if (_networkSession == null && SignedInGamer.SignedInGamers.Count == 1)
                {
                    JoinSession();
                }
            }

            // Waiting for gamers to connect / gamers to get ready by updating session.
            if (State == NetworkState.Waiting)
            {
                if (_networkSession != null)
                {
                    _networkSession.Update();
                }
            }

            // Playing the game, sending packets and updating the session.
            if (State == NetworkState.Playing)
            {
                if (_networkSession != null)
                {
                    if (CheckPlayerStatus(0) && CheckPlayerStatus(1))
                    {
                        UpdateSession(gt);
                    }
                }
                _networkSession.Update();
            }

            // Shuts down the session, clears everything and goes into offline state.
            if (State == NetworkState.Ending)
            {
                if (_networkSession != null)
                {
                    _networkSession.Dispose();
                    _networkSession = null;
                    Gamers = new List<Gamer>();
                    State = NetworkState.Offline;
                }
            }
        }

        #region SessionManagement
        /// <summary>
        /// Creates a new session, hooks on the eventhandlers and changes state.
        /// </summary>
        void CreateSession()
        {
            try
            {
                _networkSession = NetworkSession.Create(NetworkSessionType.PlayerMatch, _maxLocalGamers, _maxNetworkGamers);

                
                Status = "Created session";
                State = NetworkState.Waiting;
                // Subscribe to events.
                HookSessionEvents();
            }
            catch (Exception)
            {
                Status = "Error CreateSession";
            }
        }

        /// <summary>
        /// Joins the first valid session available, hooks on the eventhandlers and changes state.
        /// </summary>
        void JoinSession()
        {
            try
            {
                using (_availableNetworkSessions = NetworkSession.Find(NetworkSessionType.PlayerMatch, _maxLocalGamers, null))
                {
                    if (_availableNetworkSessions.Count == 0)
                    {
                        Status = "No sessions available";
                        State = NetworkState.Ending;
                        return;
                    }

                    _networkSession = NetworkSession.Join(_availableNetworkSessions[0]);

                     Status = ("Joined a session hosted by: " + _networkSession.Host.Gamertag);
                    
                     State = NetworkState.Waiting;

                    // Subscribe to events.
                    HookSessionEvents();
                }
            }
            catch (Exception e)
            {
                 Status = "Error JoinSession: " + e.ToString();
                 State = NetworkState.Ending; 
            }
        }

        /// <summary>
        /// Updates the session, sends and recieves packets for each gamer. Also sets simulation values.
        /// </summary>
        /// <param name="gt">The gameTime.</param>
	    public void UpdateSession(GameTime gt)
	    {
            if (GameEngine.Instance.Scene.PeekScene().Network)
            {
                foreach (LocalNetworkGamer gamer in _networkSession.LocalGamers)
                {
                    SendPackets(gamer);
                }
                foreach (LocalNetworkGamer gamer in _networkSession.LocalGamers)
                {
                    ReadPackets(gamer);
                }

                _networkSession.SimulatedLatency = new TimeSpan(0, 0, 0, 0, _ms);
                _networkSession.SimulatedPacketLoss = _packetLoss;

                GameEngine.Instance.Debug.SetData("Sent : ",_networkSession.BytesPerSecondSent.ToString());
                GameEngine.Instance.Debug.SetData("Recieved: ", _networkSession.BytesPerSecondReceived.ToString());
	        }
	    }
        #endregion

        #region NetworkPackageHandling
        /// <summary>
        /// Sends the packets for a gamer.
        /// </summary>
        /// <param name="gamer">The gamer.</param>
        public void SendPackets(LocalNetworkGamer gamer)
        {
            GameNetwork.NetPacketWriter(_packetWriter, gamer);
        }

        /// <summary>
        /// Reads the packets for a gamer.
        /// </summary>
        /// <param name="gamer">The gamer.</param>
        public void ReadPackets(LocalNetworkGamer gamer)
        {
           GameNetwork.NetPacketReader(_packetReader, gamer);
        }
        #endregion

        #region SessionEventHandlers
        /// <summary>
        /// Hooks the session events to the session.
        /// </summary>
        private void HookSessionEvents()
        {
            _networkSession.GamerJoined += GamerJoinedEventHandler;
            _networkSession.SessionEnded += SessionEndedEventHandler;
            _networkSession.GamerLeft += GamerLostEventHandler;
        }

        /// <summary>
        /// Eventhandler for when the session is lost.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="NetworkSessionEndedEventArgs" /> instance containing the event data.</param>
        private void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
        {
            Status = "Session lost";
            State = NetworkState.Ending;
        }

        /// <summary>
        /// Eventhandler for when a gamer is lost from the session.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="GamerLeftEventArgs" /> instance containing the event data.</param>
        private void GamerLostEventHandler(object sender, GamerLeftEventArgs e)
        {
           Status = " Gamer Left: " + e.Gamer.Gamertag;
           State = NetworkState.Ending;
        }

        /// <summary>
        /// Eventhandler for when a gamer joins the session.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="GamerJoinedEventArgs" /> instance containing the event data.</param>
        private void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
        {
            Status = "Gamer Joined: " + e.Gamer.Gamertag;
            Gamers.Add(e.Gamer);
        }
        #endregion

        #region HelperMethods
        /// <summary>
        /// Checks the player status to see if its ready or not.
        /// </summary>
        /// <param name="index">The index of the player.</param>
        /// <returns></returns>
        public bool CheckPlayerStatus(int index)
        {
            return _networkSession.AllGamers[index].IsReady;
        }

        /// <summary>
        /// Switches the state of the player to ready if its not and vice versa.
        /// </summary>
        public void SwitchState()
        {
            _networkSession.LocalGamers[0].IsReady = !_networkSession.LocalGamers[0].IsReady;
        }

        /// <summary>
        /// Checks if the player is the local gamer.
        /// </summary>
        /// <param name="index">The index of the local player.</param>
        /// <returns></returns>
        public bool CheckLocal(int index)
        {
            return _networkSession.AllGamers[index].IsLocal;
        }

        /// <summary>
        /// Checks if the local player/machine is the host.
        /// </summary>
        /// <returns></returns>
        public bool CheckHost()
        {
            return _networkSession.IsHost;
        }

        /// <summary>
        /// Increases or decreases the simulated MS delay by 50ms.
        /// </summary>
        /// <param name="increase">if set to <c>true</c> [increase].</param>
        public void IncreaseSimulatedMS(bool increase)
        {
            if (increase)
            {
                _ms += 50;
            }
            else
            {
                if (_ms > 0)
                {
                    _ms -= 50;
				}
            }
            GameEngine.Instance.Debug.SetData("MS: ", _ms.ToString());
        }

        /// <summary>
        /// Increases or decreases the packet loss by 10% .
        /// </summary>
        /// <param name="increase">if set to <c>true</c> [increase].</param>
        public void IncreasePacketLoss(bool increase)
        {
            if (increase)
            {
                if (!(_packetLoss < 0.9f))
                {
                    _packetLoss += 0.1f;
                }
            }
            else
            {
                if (_packetLoss > 0)
                {
                    _packetLoss -= 0.1f;
                }
            }
            GameEngine.Instance.Debug.SetData("PacketLoss: ", _packetLoss.ToString() + " %");
        }
        #endregion
    }
}
